package listen;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import Logging.DateStamp;
import Logging.LogWriter;
import Users.TaskData;
import Users.UserAccount;
import Users.GroupTask;

// Multi-threaded server. Receives request and sends "200 response to client.
public class Listen {
	private ServerSocket server;
	private Socket client = null;
	private static int threadID = 0;
	private ExecutorService threadPoolExecutor;
	private final int portNumber = 9226;
	private final int maxThreads = 10;
	private final LogWriter serverLog = new LogWriter();

	public static int userAccountCounter = 100; // Assigns unique userID to each new user account.
	public static int groupTaskCounter = 100; // Assigns unique group task ID to each new group task.
	private HashMap<String, UserAccount> userList = new HashMap<String, UserAccount>(); // Holds valid user's and account info.
	private HashMap<String, String> validUserAccounts = new HashMap<String, String>(); // Holds valid user's and passwords.
	private ArrayList<GroupTask> group = new ArrayList<GroupTask>(); // Holds all group tasks.

	public void webServerRun() throws InterruptedException {
		loadDummyAccount();
		// Open server socket.
		try {
			server = new ServerSocket(portNumber);
			serverLog.log("Listening to port " + portNumber + ".");
		} catch (IOException ie) {
			System.out.println("Failed to open socket on port " + portNumber + ": " + ie);
			System.exit(-1);
		}

		// Set up thread pool.
		threadPoolExecutor = Executors.newFixedThreadPool(maxThreads);

		// Listen for client.
		while (true) {
			try {
				client = server.accept();
				threadPoolExecutor.execute(new RequestThread(client, threadID, validUserAccounts, userList, group));

				// Cycles through thread pool identifiers.
				if (threadID == maxThreads) {
					threadID = 0;
				} else {
					threadID++;
				}
			} catch (IOException ie) {
				serverLog.setThreadID(threadID);
				serverLog.log("failed with client (" + client.getInetAddress());
				System.exit(-1);
			}
		}
	}

	public static void main(String[] args) throws IOException, InterruptedException {
		Listen listenServer = new Listen();
		listenServer.webServerRun();
	}

	public void loadDummyAccount() {
		UserAccount user = new UserAccount(userAccountCounter++, "DF", "pass");
		validUserAccounts.put(user.getUserName(), user.getPassword());
		user.addTask(new TaskData("DF\n21\n-1\nHomework\nHome\n200\n-200\nfalse\ntrue\n<body>Update project log.\nAdd new menu.<body><taskbreak>\n"));
		user.addTask(new TaskData("DF\n22\n-1\nOil Change\nMechanic\n300\n-300\nfalse\ntrue\n<body>Use coupon for oil change.<body><taskbreak>"));
		user.addTask(new TaskData("DF\n23\n-1\nMovie\nTheater\n400\n-400\nfalse\ntrue\n<body>Pay for movie tickets.<body><taskbreak>"));
		userList.put(user.getUserName(), user);
		
		user = new UserAccount(userAccountCounter++, "tester", "pass");
		validUserAccounts.put(user.getUserName(), user.getPassword());
		userList.put(user.getUserName(), user);
		
		user = new UserAccount(userAccountCounter++, "Kate", "pass");
		validUserAccounts.put(user.getUserName(), user.getPassword());
		userList.put(user.getUserName(), user);
		
		user = new UserAccount(userAccountCounter++, "Dennis", "pass");
		validUserAccounts.put(user.getUserName(), user.getPassword());
		userList.put(user.getUserName(), user);
				
		GroupTask newGroupTask = new GroupTask(groupTaskCounter++, new TaskData("Group\n-1\n-1\nGroceries\nSafeway\n800\n-800\nfalse\ntrue\n<body>paper towels\ntoothpaste\ncarrots<body><taskbreak>"));
		newGroupTask.addMember("DF");
		newGroupTask.addMember("tester");
		group.add(newGroupTask);
		newGroupTask = new GroupTask(groupTaskCounter++, new TaskData("Group\n-1\n-1\nTeam Meeting\nWork\n900\n-900\nfalse\ntrue\n<body>Personnel problems\nraises\nnew coffee maker<body><taskbreak>"));
		newGroupTask.addMember("tester");
		group.add(newGroupTask);
	}
}

class RequestThread implements Runnable {
	private Socket client;
	private BufferedInputStreamReader requestStream;
	private BufferedOutputStream out;
	private int threadID;

	// private UserAccount user;
	// private ArrayList<TaskData> taskListData;
	private HashMap<String, String> validUserAccounts;
	private HashMap<String, UserAccount> userList;
	private ArrayList<GroupTask> group;
	private LogWriter serverLog = new LogWriter();

	public RequestThread(Socket clientSocket, int tID, HashMap<String, String> validUserAccounts1, HashMap<String, UserAccount> userList1, ArrayList<GroupTask> group1) {
		this.client = clientSocket;
		this.threadID = tID;

		// user = user1;
		// taskListData = taskListData1;
		validUserAccounts = validUserAccounts1;
		userList = userList1;
		group = group1;

		serverLog.setThreadID(tID);

		try {
			requestStream = new BufferedInputStreamReader(new BufferedInputStream(client.getInputStream()));
			out = new BufferedOutputStream(client.getOutputStream());
		} catch (IOException ie) {
			serverLog.log("Error in Request Thread: " + ie);
		}
	}

	// Process client request.
	public void run() {
		try {
			BufferedReader fromClient = new BufferedReader(new InputStreamReader(client.getInputStream()));

			// Reads the first line to get the request method, URI and HTTP
			// version
			String line = fromClient.readLine();

			System.out.println(line); // Debug
			line = line.replace("GET /", "");
			line = line.replace(" HTTP/1.1", "");
			System.out.println(line); // Debug

			String firstLine[] = line.split("<CMD>");

			serverLog.log("Receiving from Client (" + client.getInetAddress() + ")");
			serverLog.log("Client sent: " + line);

			// Read Headers. Not important. Could just ignore rest of input.
			// Only above first line is important.
			//int x = 3;
			//while (x-- > 0) {
			//	line = fromClient.readLine();
			//	serverLog.log(line);
			//}

			String requestCommand = firstLine[0];
			String sendText = "";

			if (requestCommand.compareTo("HEADERS") == 0) {
				serverLog.log("--User Headers Requested.--");
				sendText = "<UserID>\n<GroupID>\n<Title>\n<TaskData>\n<Address>\n<Latitude>\n<Longitude>\n";
			} else if (requestCommand.compareTo("UPDATE") == 0) {
				sendText = requestUPDATE(firstLine);
			} else if (requestCommand.contentEquals("ADD")) {
				requestADD(firstLine);
			} else if (requestCommand.contentEquals("DELETE")) {
				requestDELETE(firstLine);
			} else if (requestCommand.contentEquals("GROUPDELETE")) {
				requestGROUPDELETE(firstLine);
			} else if (requestCommand.contentEquals("LOGIN")) {
				sendText = requestLOGIN(firstLine);
			} else if (requestCommand.contentEquals("EDIT")) {
				requestEDIT(firstLine);
			} else if (requestCommand.contentEquals("GROUPUSERS")) {
				sendText = requestGROUPUSERS(firstLine);
			} else if (requestCommand.contentEquals("MEMBERS")) {
				sendText = requestMEMBERS(firstLine);
			} else if (requestCommand.contentEquals("ALLUSERS")) {
				sendText = requestALLUSERS();
			} else if (requestCommand.contentEquals("SAVEGROUPUSERS")) {
				requestSAVEGROUPUSERS(firstLine);
			} else if (requestCommand.contentEquals("NEWGROUP")) {
				sendText = requestNEWGROUP(firstLine);
			}

			/*
			 * try { System.out.println("request" + requestCommand);
			 * 
			 * Method method =
			 * Class.forName("listen.RequestThread").getMethod("requestUPDATE",
			 * String.class); System.out.println(method); method.invoke(null,
			 * firstLine[1]); } catch (IllegalAccessException e) {
			 * serverLog.log("Improper Access by method call: " + e); } catch
			 * (IllegalArgumentException e) {
			 * serverLog.log("Wrong argument for method call: " + e); } catch
			 * (InvocationTargetException e) {
			 * serverLog.log("Invocation target fault: " + e); } catch
			 * (ClassNotFoundException e) { serverLog.log("Class not found: " +
			 * e); } catch (NoSuchMethodException e) {
			 * serverLog.log("Missing method called: " + e); }
			 */

			sendResponse(sendText);
		} catch (IOException ie) {
			serverLog.log("IO failed: " + ie);
			System.exit(-1);
		}
	}

	// Send back to client any data. Currently on sends "200" message.
	private void sendResponse(String sendText) {
		DateStamp date = new DateStamp();

		try {
			BufferedOutputStream outputToClient = new BufferedOutputStream(client.getOutputStream());
			PrintWriter writer = new PrintWriter(outputToClient, true);
			serverLog.setWriter(writer);

			serverLog.log("Sending to Client ");

			serverLog.sendToClient("HTTP/1.1 200 OK");
			serverLog.sendToClient("Server: LISTEN");
			serverLog.sendToClient("Date: "	+ date.printDate("EEE, d MMM yyyy HH:mm:ss z"));
			serverLog.sendToClient("Content-Type: text/plain");
			serverLog.sendToClient("Connection: close");
			serverLog.sendToClient("Content-Length: " + sendText.length());
			serverLog.sendToClient("Age: 0");

			writer.println("");
			writer.println(sendText + "\n\r");

			writer.flush();
			writer.close();

			serverLog.log("Closed connection with " + client.getInetAddress());

			out.close();
			client.close();
		} catch (IOException ex) {
			serverLog.log("Access Failed (" + client.getInetAddress() + ": " + ex.getMessage());
		}
	}

	private String convertNewline(String input) {
		return input.replace("<CR>", "\n");
	}

	private void addUserAccount(String userName, String password) {
		UserAccount user = new UserAccount(Listen.userAccountCounter++,	userName, password);
		validUserAccounts.put(user.getUserName(), user.getPassword());
		userList.put(user.getUserName(), user);
		serverLog.log("New User added: " + userName);
	}

	/*
	 * These classes are all for handling client requests. The format: set +
	 * <client request> e.g. set + UPDATE = setUPDATE()
	 */

	private String requestUPDATE(String[] line) {
		serverLog.log("--User Update Requested.--");
		String returnLine = userList.get(line[1]).getTasksToString();
		for (int x = 0; x < group.size(); x++) {
			if (group.get(x).isMember(line[1])) {
				serverLog.log("Found, " + line[1] + " member of group task id "	+ group.get(x).getGroupTaskID());
				returnLine += group.get(x).getTask().toString();
			}
		}
		return returnLine;
	}

	private void requestADD(String[] line) {
		serverLog.log("--User Added Task--");
		userList.get(line[1]).addTask(new TaskData(convertNewline(line[2])));
	}

	private void requestEDIT(String[] line) {
		System.out.println(line[2]);
		TaskData task = new TaskData(convertNewline(line[2]));
		if (task.getGroupID() < 0) {
			if (userList.get(line[1]).editTask(task)) {
				serverLog.log("--User Edited Task--");
				serverLog.log("User (" + line[1] + ") edited task "	+ task.getTaskId() + ": " + task.getTitle());
			} else {
				serverLog.log("Error: Failed to update User (" + line[1] + ") edit task " + task.getTaskId() + ": "	+ task.getTitle());
			}
		} else {
			for(int x = 0; x < group.size(); x++) {
				if(group.get(x).getGroupTaskID() == task.getGroupID()) {
					group.get(x).editTask(task);
					serverLog.log("--Group Task Edited--");
					serverLog.log("User (" + line[1] + ") edited group task " + task.getGroupID() + ": " + task.getTitle());
					return;
				}
			}
			serverLog.log("Error: Failed to edit group task " + task.getGroupID() + ": "	+ task.getTitle());
		}
	}

	private void requestDELETE(String[] line) {
		// System.out.println("In Delete: " + line);
		// for(int x = 0; x < line.length; x++)
		// System.out.println("In Delete: " + line[x]);

		// String userName = line[1];
		int taskID = Integer.valueOf(line[2]);
		userList.get(line[1]).getTaskList().remove(userList.get(line[1]).findTaskIDLocation(taskID));
	}
	
	private void requestGROUPDELETE(String[] line) {
		String user = line[1];
		int groupID = Integer.valueOf(line[2]);
		int index = 0;
		
		serverLog.log("User " + user + " requesting removal from group task " + groupID + ".");
		while((index < group.size()) && (group.get(index).getGroupTaskID() != groupID)) {
			index++;
		}
		
		if(group.get(index).removeMember(user)) {
			serverLog.log("User " + user + " removed from group task " + groupID + ".");
		} else {
			serverLog.log("Failed to remove " + user + " from group task " + groupID + ".");
		}
		
		// Remove group task from list if no more members are attached to task.
		if(group.get(index).noMembers()) {
			group.remove(index);
			serverLog.log("Group task " + groupID + " has no members. Deleting.");
		}
	}

	private String requestLOGIN(String[] line) {
		String name = line[1];
		String pwd = line[2];

		if (validUserAccounts.containsKey(name)) {
			serverLog.log("User (" + name + ") attempting to login.");
			if (validUserAccounts.get(name).equals(pwd)) {
				serverLog.log("User (" + name + ") logged in.");
			} else {
				serverLog.log("User (" + name + ") failed to login.");
				return "BAD";
			}
		} else {
			serverLog.log("New User: " + name);
			addUserAccount(name, pwd);
			return "NEW";
		}
		return "GOOD";
	}

	private String requestGROUPUSERS(String[] line) {
		int index = 0;
		int groupID = Integer.valueOf(line[1]);;
		serverLog.log("Requesting group " + groupID + " members.");
		while((index < group.size()) && (group.get(index).getGroupTaskID() != groupID)) {
			index++;
		}
		serverLog.log("Group " + groupID + " found.");
		return group.get(index).getMembersToString();
	}
	
	private String requestMEMBERS(String[] line) {
		int index = 0;
		int groupID = Integer.valueOf(line[1]);;
		serverLog.log("Requesting group " + groupID + " members for viewing.");
		while((index < group.size()) && (group.get(index).getGroupTaskID() != groupID)) {
			index++;
		}
		serverLog.log("Group " + groupID + " found.");
		return group.get(index).getMembersToStringOneLine();
	}
	
	private String requestALLUSERS() {
		serverLog.log("Requesting all users.");
		String returnText = "";
		Iterator user = validUserAccounts.keySet().iterator();
		
		while(user.hasNext()) {
			returnText += user.next() + "\n";
		}
		
		return returnText;
	}
	
	private void requestSAVEGROUPUSERS(String[] line) {
		int groupID = Integer.valueOf(line[1]);
		int index = 0;
		
		serverLog.log("Updating group members for group task " + groupID + ".");
		
		while((index < group.size()) && (group.get(index).getGroupTaskID() != groupID)) {
			index++;
		}
		
		String[] members = line[2].split("<CR>");
		group.get(index).setMembers(members);
	}
	
	private String requestNEWGROUP(String[] line) {
		GroupTask newGroupTask = new GroupTask(Listen.groupTaskCounter, new TaskData("Group\n-1\n-1\n\n\n0\n0\nfalse\ntrue\n<body><body><taskbreak>"));
		newGroupTask.addMember(line[1]);
		group.add(newGroupTask);
		return newGroupTask.getTask().toString();
	}
}